qtre.ColladaLoader = function ()
{
	var XHR_STATE_COMPLETED = 4;
	var xmlhttp = null;
	this.done = false;
	this.name = "";
	this.rootNode = null;

	this.rootPath = "";

	this.textures = {};

	this.load = function (relativePath, rootNode, camera, callback)
	{
		this.rootNode = rootNode;
	
		this.camera = camera;

		xmlhttp = new XMLHttpRequest();

		xmlhttp.parent = this;

		xmlhttp.callbackFunc = this.parse;
		xmlhttp.open("GET", relativePath, true);
		xmlhttp.overrideMimeType('text/xml');
		
		//取出模型所在的根目录
		var pathSplited = relativePath.split("/");
		pathSplited.pop();
		this.rootPath = pathSplited.join("/") + "/";
		
		try
		{
			xmlhttp.send(null);
		}
		catch (err)
		{
			tdl.log("Could not find file '" + relativePath + "'. Check the path.");
		}

		xmlhttp.onreadystatechange = function ()
		{
			if (xmlhttp.readyState == XHR_STATE_COMPLETED)
			{
				if (xmlhttp.responseXML)
				{
					xmlhttp.responseXML.colladaPath = relativePath;

					this.callbackFunc(xmlhttp.responseXML);

					callback(rootNode);
				}
			}
		}

		return this.rootNode;
	}

	this.parseNodeRecursive = function (xmlObject, node, sgNode)
	{
		// set this node's transform
		var translateTag = qtre.ColladaLoader.getChildNodesByNodeName(node, "translate");

		// node may not have one, so check first
		if (translateTag)
		{
			// string representation of data between <translate> tags.
			var floatValues = qtre.ColladaLoader.stringsToFloats(translateTag[0].childNodes[0].nodeValue, ' ');

			if (xmlObject.upAxis && xmlObject.upAxis == "Z_UP")
			{
				var temp = floatValues[1];
				floatValues[1] = floatValues[2];
				floatValues[2] = -temp;
			}
			else if (xmlObject.upAxis && xmlObject.upAxis == "X_UP")
			{
				var temp = floatValues[0];
				floatValues[0] = -floatValues[1];
				floatValues[1] = temp;
			}
			sgNode.translate(floatValues);
		}

		// rotations
		var rotationTags = qtre.ColladaLoader.getChildNodesByNodeName(node, "rotate");

		if (rotationTags)
		{
			// example
			// <rotate sid="rotateZ">0 0 1 15</rotate>
			// <rotate sid="rotateY">0 1 0 0</rotate>
			// <rotate sid="rotateX">1 0 0 0</rotate>
			for (var i = 0, len = rotationTags.length; i < len; i++)
			{
				var floatValues = qtre.ColladaLoader.stringsToFloats(rotationTags[i].childNodes[0].nodeValue, ' ');

				var vec = [floatValues[0], floatValues[1], floatValues[2]];

				if (xmlObject.upAxis && xmlObject.upAxis == "Z_UP")
				{
					var temp = vec[1];
					vec[1] = vec[2];
					vec[2] = -temp;
				}
				else if (xmlObject.upAxis && xmlObject.upAxis == "X_UP")
				{
					var temp = vec[0];
					vec[0] = -vec[1];
					vec[1] = temp;
				}

				var angle = tdl.math.degToRad(floatValues[3]);

				sgNode.rotate(vec, angle);
			}
		}

		// <scale> tag
		var scaleTag = qtre.ColladaLoader.getChildNodesByNodeName(node, "scale");
		if (scaleTag)
		{
			var floatValues = qtre.ColladaLoader.stringsToFloats(scaleTag[0].childNodes[0].nodeValue, ' ');

			if (xmlObject.upAxis && xmlObject.upAxis == "Z_UP")
			{
				var temp = floatValues[1];
				floatValues[1] = floatValues[2];
				floatValues[2] = temp;
			}
			if (xmlObject.upAxis && xmlObject.upAxis == "X_UP")
			{
				var temp = floatValues[0];
				floatValues[0] = floatValues[1];
				floatValues[1] = temp;
			}

			sgNode.scale(floatValues);
		}

		var matrixTag = qtre.ColladaLoader.getChildNodesByNodeName(node, "matrix");
		if (matrixTag)
		{
			var mat = qtre.ColladaLoader.stringsToFloats(matrixTag[0].childNodes[0].nodeValue, ' ');

			// If Z-axis is up, any rotations on Y and Z need to be flipped.
			if (xmlObject.upAxis && xmlObject.upAxis == "Z_UP")
			{
				// swap y, z translation
				var temp = mat[7];
				mat[7] = mat[11];
				mat[11] = -temp;

				temp = mat[1];
				mat[1] = mat[2];
				mat[2] = temp;

				temp = mat[4];
				mat[4] = mat[8];
				mat[8] = temp;

				temp = mat[5];
				mat[5] = mat[10];
				mat[10] = temp;
			}

			// If X-axis is up, any rotations on X and Y needs to be flipped.
			if (xmlObject.upAxis && xmlObject.upAxis == "X_UP")
			{
				// swap x, y translation
				var temp = mat[3];
				mat[3] = -mat[7];
				mat[7] = temp;

				temp = mat[0];
				mat[0] = mat[5];
				mat[5] = temp;

				temp = mat[2];
				mat[2] = mat[6];
				mat[6] = temp;

				temp = mat[8];
				mat[8] = mat[9];
				mat[9] = temp;
			}
			tdl.fast.matrix4.transpose(mat, mat);
			sgNode.worldMatrix = mat;
		}

		var geometries = qtre.ColladaLoader.getChildNodesByNodeName(node, "instance_geometry");

		if (geometries)
		{
			for (var currGeo = 0, len = geometries.length; currGeo < len; currGeo++)
			{
				var url = geometries[currGeo].getAttribute("url").split('#')[1];

				sgNode.attachObject(this.instantiateGeometry(xmlObject, url, geometries[currGeo]));
			}
		}

		var instance_nodes = qtre.ColladaLoader.getChildNodesByNodeName(node, "instance_node");
		if (instance_nodes)
		{
			for (var currNode = 0, len = instance_nodes.length; currNode < len; currNode++)
			{
				var url = instance_nodes[currNode].getAttribute("url").split('#')[1];
				sgNode.addChild(this.instantiateNode(xmlObject, url));
			}
		}

		var nodes = qtre.ColladaLoader.getChildNodesByNodeName(node, "node");

		// recursive case: the node has one or many nodes, therefore
		// we have to parse all of its nodes.
		if (nodes)
		{
			// for each of subnodes, call this function.
			for (var i = 0, len = nodes.length; i < len; i++)
			{
				var scenenode = qtre.sceneManager.createSceneNode(nodes[i].getAttribute("name"));
				sgNode.addChild(scenenode);

				// call this function for each of the nodes found.
				this.parseNodeRecursive(xmlObject, nodes[i], scenenode);
			}
		}
	}

	this.getChoice = function (parentTag, choiceTagNames)
	{
		var choice = null;
		var i = 0;

		while (choice == null && i < choiceTagNames.length)
		{
			choice = parentTag.getElementsByTagName(choiceTagNames[i])[0];
			i++;
		}

		return choice;
	}

	this.parse = function (xmlObject)
	{
		var loader = this.parent;

		var root = xmlObject.documentElement;

		var library_images = root.getElementsByTagName("library_images");

		for (var libraryImagesIter = 0, len = library_images.length; libraryImagesIter < len; libraryImagesIter++)
		{
			var imageElements = library_images[libraryImagesIter].getElementsByTagName("image");

			for (var imageElementIter = 0, len2 = imageElements.length; imageElementIter < len2; imageElementIter++)
			{
				var init_from = imageElements[imageElementIter].getElementsByTagName("init_from")[0];
				//load image
				var url = init_from.childNodes[0].nodeValue;
				var file = url.split("/").pop();
				file = url.split("\\").pop();
				//默认在模型所在目录下寻找图片
				var path = loader.rootPath + file;
				//先保存图片
				loader.textures[imageElements[imageElementIter].getAttribute("id")] = tdl.textures.loadTexture(path);
			}
		}

		var upAxisTag = root.getElementsByTagName("up_axis")[0];
		if (upAxisTag)
		{
			xmlObject.upAxis = upAxisTag.childNodes[0].nodeValue;
		}

		var sceneElement = root.getElementsByTagName("scene")[0];

		var instanceVisualSceneElem = sceneElement.getElementsByTagName("instance_visual_scene")[0];

		var visualSceneToLoad = instanceVisualSceneElem.getAttribute("url").split('#')[1];

		var libraryVisualScenes = root.getElementsByTagName("library_visual_scenes")[0];

		var visualSceneList = libraryVisualScenes.getElementsByTagName("visual_scene");

		var visualScene = null;

		// go over all the visual scenes trying to identify the one we want.
		for (var i = 0, len = visualSceneList.length; i < len; i++)
		{
			if (visualSceneList[i].getAttribute("id") == visualSceneToLoad)
			{
				visualScene = visualSceneList[i];
			}
		}

		var nodes = qtre.ColladaLoader.getChildNodesByNodeName(visualScene, "node");

		if (nodes)
		{
			// parse each of the 'root' nodes.
			for (var currNode = 0, len = nodes.length; currNode < len; currNode++)
			{
				var scenenode = qtre.sceneManager.createSceneNode(nodes[currNode].getAttribute("name"));
		
				loader.rootNode.addChild(scenenode);
				loader.parseNodeRecursive(xmlObject, nodes[currNode], scenenode);
			}
		}
	
		delete xmlObject;
		delete xmlhttp;
	}

	this.instantiateMaterial = function (xmlObject, target)
	{
		var tempTexture = null;

		// we now have the material ID which we can look up in the library materials.
		var material = this.findElementInLibrary(xmlObject, "library_materials", "material", target);
		if(!material){
			return null;
		}
		var tempName = target;
		var materialInstance = qtre.sceneManager.getMaterial(tempName);
		if(materialInstance){
			return materialInstance;
		}

		var instanceEffect = material.getElementsByTagName("instance_effect")[0];
		var instanceEffectURL = instanceEffect.getAttribute("url").split('#')[1];

		var effect = this.findElementInLibrary(xmlObject, "library_effects", "effect", instanceEffectURL);

		var profile_COMMON = effect.getElementsByTagName("profile_COMMON")[0];

		var technique = profile_COMMON.getElementsByTagName("technique")[0];

		// get the texture
		var newparam = profile_COMMON.getElementsByTagName("newparam")[0];
		//这个newparam是做什么的！？
		if (newparam)
		{
			// go to the surface type
			var surface = newparam.getElementsByTagName("surface")[0];

			// get the value between the <init_from> tags
			// the plane would have:
			// <init_from>file1</init_from>
			// it also has a format, but we ignore this for now.
			var init_from = surface.getElementsByTagName("init_from")[0];

			// got the file id.
			var fileID = init_from.childNodes[0].nodeValue;

			// file1 is an id of an image in the <library_images> library
			var texture = this.findElementInLibrary(xmlObject, "library_images", "image", fileID);

			// finally, get the image name
			//<image id="file2" name="file2" depth="1">
			//	<init_from>./duckCM.tga</init_from>
			//</image>
			var textureName = texture.getElementsByTagName("init_from")[0].childNodes[0].nodeValue;

			var resolvedTexture;

			// if the texture is an abosolute path, use it.
			if (c3dl.isPathAbsolute(textureName))
			{
				resolvedTexture = textureName;
			}
			// otherwise, we need to place the path of dae file before the texture.
			else
			{
				resolvedTexture = c3dl.getPathWithoutFilename(xmlObject.colladaPath) + textureName;
			}
			tempTexture = resolvedTexture;
		}

		// get the shading algorithm used.
		// as of right now, we aren't concerned with the algorithm itself, (we use our
		// own custom shading algorithm) but what we want to extract are the properties
		// of the shading method which include diffuse, ambient, specular, etc. components.
		var shadingAlgorithm = this.getChoice(technique, ["blinn", "constant", "phong", "lambert"]);
		
		var material = qtre.sceneManager.createMaterial(tempName, "phong");
		var diffuse = this.getColor(shadingAlgorithm, "diffuse");
		var shininess = this.getColor(shadingAlgorithm, "shininess");
		
		material.updateUniforms({
			diffuseSampler : diffuse,
			shininess : shininess
		})

		material.bindCamera(this.camera);
		return material;
	}

	/**
   @private
   
   The root COLLADA can contain ZERO OR MANY library_geometries node.
   However in our case, since we are importing model data, should
   have at least one library_geometries.  If absent this will cause
   an error.
   
   The library geometries node contains 1 or Many <geometry>
   nodes.
   If library_geometries describes a car,
   there may be several <geometries> which described the chairs,
   seats, body, etc.
   
   @param {XMLDocument} xmlObject
   @param url
   @param instanceGeometryElement
   */
	this.instantiateGeometry = function (xmlObject, url, instanceGeometryElement)
	{
		var root = xmlObject.documentElement;
		var libraryGeometries = root.getElementsByTagName("library_geometries");

		// once not null, we can stop searching.
		var geoToCreate = null;
		var geometry = qtre.sceneManager.createRenderable(url);
		var texture = tdl.textures.loadTexture([170, 170, 170, 255]);

		// the url provided points to a geometry in a geometry library. We'll need to
		// go through the libraries and find which library it is in.
		// TODO: add breakout when found
		for (var currLib = 0, len = libraryGeometries.length; currLib < len; currLib++)
		{
			var geometries = libraryGeometries[currLib].getElementsByTagName("geometry");
			// for each geometry
			for (var currGeo = 0, len2= geometries.length; currGeo < len2; currGeo++)
			{
				if (geometries[currGeo].getAttribute("id") == url)
				{
					// found it
					geoToCreate = geometries[currGeo];
				}
			}
		}

		var verticesArray = null;
		var vertexStride;

		var normalsArray = null;
		var normalsStride;

		var texCoordsArray = null;
		var texCoordsStride;

		var faces = null;
		var rawFaces;

		var mesh = geoToCreate.getElementsByTagName("mesh")[0];

		// we'll need to iterate over all the collation elements.
		var collations = [];

		for (var i = 0, len = mesh.childNodes.length; i < len; i++)
		{
			if (mesh.childNodes[i].nodeName == "triangles" ||
				mesh.childNodes[i].nodeName == "polygons" ||
				mesh.childNodes[i].nodeName == "polylist" ||
				mesh.childNodes[i].nodeName == "lines" )
				{
				collations.push(mesh.childNodes[i]);
			}
		}

		// the collation elements have many primitives.
		// <p> element represents a primitive.
		//
		// currColl = current collation
		for (var currColl = 0, len = collations.length; currColl < len; currColl++)
		{
			if (collations[currColl].nodeName == "triangles" ||
				collations[currColl].nodeName == "polylist" ||
				collations[currColl].nodeName == "lines")
				{
				var p = this.getFirstChildByNodeName(collations[currColl], "p");
				new Float32Array(rawFaces = this.mergeChildData(p.childNodes).split(" "));
			}

			else if (collations[currColl].nodeName == "polygons")
			{
				var p_tags = collations[currColl].getElementsByTagName("p");
				var faces = [];
                       
				for (var i = 0; i < p_tags.length; i++)
				{
					// need to get rid of the spaces
					var p_line = p_tags[i].childNodes[0].nodeValue.split(" ");
					for (var j = 0; j < p_line.length; j++)
					{
						faces.push(parseInt(p_line[j]));
					}
				}
        
				rawFaces = new Float32Array(faces.length);
				for(var i = 0; i < faces.length; i++){
					rawFaces[i] = faces[i];
				}
			}
			// If this message is ever seen, that means I have to write the case for it.
			else
			{
				tdl.log(collations[currColl].nodeName + " collation element is not yet supported");
			}

			var inputs = collations[currColl].getElementsByTagName("input");

			// The order of the <input> tags can vary, so we can't rely on how they are arranged
			// in most documents.
			for (var i = 0, len2 = inputs.length; i < len2; i++)
			{ /**************/
				/*  VERTICES  */
				/**************/
				if (inputs[i].getAttribute("semantic") == "VERTEX")
				{
					this.vertexOffset = inputs[i].getAttribute("offset");
					// need to remove the leading # from the value
					this.vertexSource = inputs[i].getAttribute("source").split('#')[1];

					var vertices = qtre.ColladaLoader.getNodeWithAttribute(xmlObject, "vertices", "id",
						this.vertexSource);

					// get the child
					var input = vertices.getElementsByTagName("input")[0];

					// get the <input>s source
					var posSource = input.getAttribute("source").split('#')[1];

					// the raw data in a long list of floats, we have to group this so the face indices
					// can index into it.
					var data = this.getData(xmlObject, "source", "id", posSource);
					vertexStride = parseInt(data.stride);
					if (xmlObject.upAxis && xmlObject.upAxis == "Z_UP")
					{
						for (var vertIter = 0, len3 = data.values.length; vertIter < len3; vertIter += vertexStride)
						{
							var temp = data.values[vertIter + 1];
							data.values[vertIter + 1] = data.values[vertIter + 2];
							data.values[vertIter + 2] = -temp;
						}
					}
					else if (xmlObject.upAxis && xmlObject.upAxis == "X_UP")
					{
						for (var vertIter = 0, len3 = data.values.length; vertIter < len3; vertIter += vertexStride)
						{
							var temp = data.values[vertIter];
							data.values[vertIter] = -data.values[vertIter + 1];
							data.values[vertIter + 1] = temp;
						}
					}
					verticesArray = data.values
				}

				/**************/
				/*   NORMAL   */
				/**************/
				else if (inputs[i].getAttribute("semantic") == "NORMAL")
				{
					this.normalOffset = inputs[i].getAttribute("offset");
					// need to remove the leading # from the value
					this.normalSource = inputs[i].getAttribute("source").split('#')[1];
					var data = this.getData(xmlObject, "source", "id", this.normalSource);
					normalsStride = parseInt(data.stride);
					// length * stride instead of literal?
					if (xmlObject.upAxis && xmlObject.upAxis == "Z_UP")
					{
						for (var vertIter = 0, len3 = data.values.length; vertIter < len3; vertIter += normalsStride)
						{
							var temp = data.values[vertIter + 1];
							data.values[vertIter + 1] = data.values[vertIter + 2];
							data.values[vertIter + 2] = -temp;
						}
					}
					else if (xmlObject.upAxis && xmlObject.upAxis == "X_UP")
					{
						for (var vertIter = 0, len3 = data.values.length; vertIter < len3; vertIter += normalsStride)
						{
							var temp = data.values[vertIter];
							data.values[vertIter] = -data.values[vertIter + 1];
							data.values[vertIter + 1] = temp;
						}
					}
					normalsArray = data.values;
				}

				/**************/
				/*  TEXCOORD  */
				/**************/
				else if (inputs[i].getAttribute("semantic") == "TEXCOORD")
				{
					this.texCoordOffset = inputs[i].getAttribute("offset");
					// need to remove the leading # from the value
					var uvSource = inputs[i].getAttribute("source").split('#')[1];
					var data = this.getData(xmlObject, "source", "id", uvSource);
					texCoordsStride = parseInt(data.stride);

					// WebGL expects the bottom left corner of the
					// texture in the top left, so we need to flip the texture coordinates
					for (var currUV = 1, len3 = data.values.length; currUV < len3; currUV += texCoordsStride)
					{
						data.values[currUV] = 1 - data.values[currUV];
					}

					// we only want 2 values stored, but we may have to stride either 2 or
					// 3 depending if a zero was added
					// 1.0 1.0 0.0
					//         ^ don't need this.
					texCoordsArray = data.values
				}
			}

			// we don't need a case for triangles since
			// now that we know how many inputs there were, we can group the faces.
			faces = this.groupScalarsIntoArray(rawFaces, inputs.length, inputs.length,collations[currColl].nodeName);
	  
			// each primitive collation element can have a material name. this name matches to the
			// <instance_material>'s symbol attribute value.
			//      collationElement.tempMaterial = collations[currColl].getAttribute("material");
			if (collations[currColl].nodeName !== "lines") {
				var positionsArray = verticesArray;
				var positions = new tdl.primitives.AttribBuffer(3, faces.length);
				var normals = new tdl.primitives.AttribBuffer(3, faces.length);
				var texCoords = new tdl.primitives.AttribBuffer(2, faces.length*3/2);
		  
				var indices = new tdl.primitives.AttribBuffer(3, faces.length/3, 'Uint16Array');

				for(var i = 0; i < faces.length -2; i+=3){
					indices.push([faces[i][0], faces[i+1][0], faces[i+2][0]]);
					//重新调整法线和纹理坐标
					for(var j = 0; j < 3; j++){
						var positionPointer = faces[i+j][0];
						var normalPointer = faces[i+j][1];
						var texCoordPointer = faces[i+j][2];
						positions.push([positionsArray[positionPointer*vertexStride],positionsArray[positionPointer*vertexStride+1],positionsArray[positionPointer*vertexStride+2]]);
						normals.push([normalsArray[normalPointer*normalsStride], normalsArray[normalPointer*normalsStride+1], normalsArray[normalPointer*normalsStride+2]]);
						texCoords.push([texCoordsArray[texCoordPointer*2], texCoordsArray[texCoordPointer*2+1]]);
					}
				}
				var arrays = {
					//			indices : indices,
					position : positions,
					normal : normals,
					texCoord : texCoords
				}
				geometry.addPrimitive(arrays);
			}
			else {
		//	    collationElement.initLine(verticesArray, faces , collations[currColl].nodeName);
		}
		} // end iterating over collations
	
		// get the texture for the geometry
		// go back to the instance_geometry
		// <instance_geometry> has either 0 or 1 <bind_material>, so just get the first if it exsits.
		var bind_material = instanceGeometryElement.getElementsByTagName("bind_material")[0];
		if (bind_material)
		{
			// <bind_material> has exactly 1 <technique_common>, so only get the first.
			var technique_common = bind_material.getElementsByTagName("technique_common")[0];

			// technique_common within instance geometry may have many materials.
			var instance_materials = technique_common.getElementsByTagName("instance_material");

			// iterate over all the <instance_material>'s <technique_common> has.
			for (var im = 0, len2 = instance_materials.length; im < len2; im++)
			{
				// target is the target material to instantiate
				var target = instance_materials[im].getAttribute("target").split('#')[1];
				// symbol links to the primitive collation's material attribute.
				var symbol = instance_materials[im].getAttribute("symbol");

				// we now have the material ID which we can look up in the library materials.
				var material = this.findElementInLibrary(xmlObject, "library_materials", "material", target);

				//
				var material = this.instantiateMaterial(xmlObject, target);
				if(!material){
					continue;
				}
				
				geometry.bindMaterial(material);
			// The material was instantiated and now we have to iterate over the collations
			// and find the collation which has a material which matches the material name.
			/*
		for (var ic = 0, len = GeoCollations.length; ic < len; ic++)
        {
          if (GeoCollations[ic].tempMaterial == symbol)
          {
            GeoCollations[ic].setMaterial(instanceMaterial);
            GeoCollations[ic].setTexture(tex);
          }
        }
			  */
			} // instance_materials loop
		} // bind_material conditional
	
		return geometry;
	}

	/**
   @private
   @param {XMLDocument} xmlObject
   @param {String} url
   
   @returns {c3dl.SceneNode}
   */
	this.instantiateNode = function (xmlObject, url)
	{
		var root = xmlObject.documentElement;
		var libraryNodes = root.getElementsByTagName("library_nodes");

		// domNode
		var nodeToCreate = null;

		// <collada> may have many <library_node>. We'll need to go through each to find
		// the node with the name we are looking for.
		for (var currLib = 0, len = libraryNodes.length; currLib < len /*&& nodeToCreate == null*/ ; currLib++)
		{
			// get all the nodes in this library.
			var nodes = libraryNodes[currLib].getElementsByTagName("node");

			// find the node in the list.
			for (var currNode = 0, len2 = nodes.length; currNode < len2 /*&& nodeToCreate == null*/ ; currNode++)
			{
				if (nodes[currNode].getAttribute("id") == url)
				{
					// found it
					nodeToCreate = nodes[currNode];
				}
			}
		}

		var inode = qtre.sceneManager.createSceneNode(nodeToCreate.getAttribute("name"));

		this.parseNodeRecursive(xmlObject, nodeToCreate, inode);

		return inode;
	}

	/**
   @private
   This function takes a list of scalar values and 
   groups them into elements inside an array.  This 
   must be done since in the DAE file, the values are
   stored one after another in a linear format. Since
   the triangles/faces use indices to access these scalars,
   we have to group them together so indexing will work 
   properly.
   
   @param {Array} rawScalarValues
   @param {int} numComponentsPerElement
   @param {int} stride
   
   @returns {Array}
   */
	this.groupScalarsIntoArray = function (rawScalarValues, numComponentsPerElement, stride)
	{
		// start off with an empty list
		var listOfArrays = [];

		//
		for (var i = 0, len = rawScalarValues.length; i < len; i += stride)
		{
			var element = new Float32Array(numComponentsPerElement);
			var counter = 0;
      
			//
			for (var j = i; j < i + numComponentsPerElement; j++)
			{
				element[counter++] = rawScalarValues[j];
			}

			listOfArrays.push(element);
		}

		return listOfArrays;
	}

	/**
   @private
   @param {XMLDocument} xmlObject
   @param {String} libraryName
   @param {String} elementName
   @param {String} elementAttributeId
   
   @returns
   */
	this.findElementInLibrary = function (xmlObject, libraryName, elementName, elementAttributeId)
	{
		// collada may have many listings of the same library.
		var libraries = xmlObject.getElementsByTagName(libraryName);

		// for all the libraries in the <collada> element
		for (libraryIter = 0, len = libraries.length; libraryIter < len; libraryIter++)
		{
			var elements = libraries[libraryIter].getElementsByTagName(elementName);

			// for all the elements in each library.
			for (elementIter = 0, len2 = elements.length; elementIter < len2; elementIter++)
			{
				// found it
				if (elementAttributeId == elements[elementIter].getAttribute("id"))
				{
					return elements[elementIter];
				}
			}
		}
		// return null if it could not be found.
		return null;
	}


	/**
   @private
   @param {} xmlObject	
   @param {String} nodeName
   @param {String} attributeKey
   @param {String} attributeValue
   
   @return 
   */
	this.getData = function (xmlObject, nodeName, attributeKey, attributeValue)
	{
		var data = new Object();

		// find node that has 'src' value as id
		var nsrc = qtre.ColladaLoader.getNodeWithAttribute(xmlObject, "source", "id", attributeValue);
		// go to the node's technique common
		var tech_common = nsrc.getElementsByTagName("technique_common")[0];

		// go to its accessor
		var accessor = tech_common.getElementsByTagName("accessor")[0];
		data.stride = accessor.getAttribute("stride");

		// get the source
		var accessorSrc = accessor.getAttribute("source").split("#")[1];

		//
		var float_array = qtre.ColladaLoader.getNodeWithAttribute(xmlObject, "float_array", "id", accessorSrc);
		// values in the DAE file are seperated with a space
		// don't use nodeValue since it will be broken up in 4096 chunks
		data.values = new Float32Array(this.mergeChildData(float_array.childNodes).split(" "));
		return data;
	}

	/**
   @private
   @param {Array} faces A 2D array which has indices.  Each index points
   to a set of vertex, normal or texture coordinates.
   
   <pre>
   // faces can look like this:
   [
   [0,0,0] , 
   [1,0,1] , 
   [2,0,2] 
   ]
   // first value in each element is vertex index.
   // second value is normal index.
   // third value is uv index.
   </pre>
   
   @param {Array} array The array to expand. For a cube, the array of vertices
   can look like:
   
   <pre>
   [1.00000,1.00000,-1.00000] , [1.00000,-1.00000,-1.00000] , [-1.00000,-1.00000,-1.00000] 
   </pre>
   
   @param offset Refers to the component we are interested in within an array
   in the faces array.  Since the faces array has indices for verts, normals and texcoords,
   using a different index gets us a index which is a 0 based index into a list of 
   coordinates.
   
   <pre>
   here is a faces array.  If we used offset=2, we would get the following indices:
   [0,0,0] , [1,0,1] , [2,0,2] 
   ^         ^         ^
   </pre>
   
   @param {int} numComponentsToExpand
   */
	this.expandFaces = function (faces, array, offset, numComponentsToExpand)
	{
		// this is a single dimensional array which hold expanded values.
		var expandedArray = new Float32Array(faces.length*3);
		var counter = 0;
		// in the nested loop, we divide the instructions into parts to
		// make it easier to read, but don't allocate these varialbes everytime
		// the loop is executed, so declare them here. This speeds up the parser
		// quite a bit.
		var face;
		var coordIndex;
		var coord;
		var floatValue;
	
		// for all the faces
		//  [0,0,0] ,   [1,0,1] ,   [2,0,2]
		//  \    /      \    /      \    /
		//    \/          \/          \/
		// currFace=0   currFace=1    ...
		for (var currFace = 0, len = faces.length; currFace < len; currFace++)
		{
			// have to be scalar, no arrays
			// each element in the faces array is another array. That second array
			// hold components.
			for (var currComp = 0; currComp < numComponentsToExpand; currComp++)
			{
				// go to a particular face:
				// [3, 1, 4]
				face = faces[currFace];
				// we might be dealing with verts, norms or textures, each has a different offset.
				// then go to a particular value inside denoted by the offset.
				// [3, 1, 4]
				//        ^
				// offset = 2 for tex
				// 4 is an index which refers to the 4th set of tex coords.
				// something like:
				// ["1.0", "0.0"]
				coordIndex = face[offset];
				// coord is a vertex coord, normal or uv coord retrieved from the
				// 'array' array.
				//
				// ["29.4787", "0", "50.5349"] -> array[0]
				// ["29.4787", "0", "50.5349"] -> array[1]
				// ...
				// this is done for each component, for textures, we have 2 components.
				// for normals, we have 3.
				if (array){
					coord = array[coordIndex][currComp];
				}
				// insert that value into the 1d array.
				expandedArray[counter++] = coord;
			}
		}
		return expandedArray;
	}

	/**
   @private
   Loading is done once all the nodes in the .DAE file have been created and placed
   into the ModelManager.
   
   @returns {bool} true if loading is done, false otherwise. 
   */
	this.doneLoading = function ()
	{
		return this.done;
	}

	/**
   @private
   
   get data from blinn, phong, lambert node
   
   @param {}
   @param {String} str
   */
	this.getColor = function (node, str)
	{
		// ambient, diffuse, specular, etc.
		var component = node != null ? node.getElementsByTagName(str)[0] : null;

		// if the component has a reference parameter, ignore it for now.
		// that component will not be used in the calculations.
		var returnValue = [0, 0, 0];

		// if the component is present (ambient, diffuse, specular)
		if (component)
		{
			var value = this.getChoice(component, ["color", "float", "texture"]);

			if (value.nodeName == "color")
			{
				returnValue = [];
				for (var currNode = 0, len = value.childNodes.length; currNode < len; currNode++)
				{
					returnValue += value.childNodes[currNode].nodeValue;
				}
				returnValue = returnValue.split(" ");
				returnValue = [parseFloat(returnValue[0]), parseFloat(returnValue[1]), parseFloat(returnValue[2])];
				returnValue = returnValue.slice(0, 3);
			}
			//
			else if (value.nodeName == "float")
			{
				returnValue = parseFloat(value.childNodes[0].nodeValue);
			}
			//
			else if (value.nodeName == "texture")
			{
				return this.textures[value.getAttribute("texture")];
			}
		}

		return returnValue;
	}



	/**
   @private
   When reading data in between tags, 
   
   There tends to be a lot of data between tags such as <float_array>
   <float_array id="Teapot-mesh-positions-array" count="1590">29.4787 0 50.5349 ..... 34.093 </float_array>
   
   We can't just read the node value contents because it is broken up into 4096 byte chunks. So 
   we use this function to merge all the chunks together.
   
   @param {String} childNodes
   
   @returns
   */
	this.mergeChildData = function (childNodes)
	{
		var values = [];
		for (var currNode = 0, len = childNodes.length; currNode < len; currNode++)
		{
			values += childNodes[currNode].nodeValue;
		}
		//去掉\n
		values = values.substring(1);
		values = values.replace(/\n/g, ' ');
		// there are some 3d authoring tools which place a trailing space after a long set of data.
		// for example:
		//
		// <float_array id="Teapot-mesh-positions-array" count="1590">29.4787 0 50.5349 ..... 34.093 </float_array>
		//
		// the trailing space causes problems as when the string is split, an undefined value
		// is placed at the end of the array.  So we will get rid of any trailing spaces here.
		//
		return values.replace(/\s+$/, '');
	}



	/**
   @private
   Get the first child of type 'nodeName' of a element 'searchNode'.
   
   @param {Object Element} searchNode - the element to search.
   @param {String} nodeName - the node to search for.
   
   @returns 
   */
	this.getFirstChildByNodeName = function (searchNode, nodeName)
	{
		for (var i = 0, len = searchNode.childNodes.length; i < len; i++)
		{
			// found it!
			if (searchNode.childNodes[i].nodeName == nodeName)
			{
				return searchNode.childNodes[i];
			}
		}
		return null;
	}

}


/**
 @private	
 
 static method of collada loader. 
 
 @param {} xmlObject
 @param {String} nodeName
 @param {String} attributeKey
 @param {String} attributeValue
 
 @returns 
 */
qtre.ColladaLoader.getNodeWithAttribute = function (xmlObject, nodeName, attributeKey, attributeValue)
{
	var nodeFound;

	// go to the root of the XML
	var root = xmlObject.documentElement;

	// get all the tags names 'nodeName'
	var elements = root.getElementsByTagName(nodeName);

	// we might have a few tags, we need to find the one with the id specified
	for (var i = 0, len = elements.length; i < len; i++)
	{
		if (elements[i].getAttribute(attributeKey) == attributeValue)
		{
			nodeFound = elements[i];
		}
	}
	return nodeFound;
}


/**
 @private
 Get the child nodes of searchNode which have the name 'nodeName'.
 This funciton is not recursive.  It only returns the <b>direct</b> children.
 
 @param {String} searchNode - the node to search.
 @param {String} nodeName - the nodes to search for.
 
 @returns {Array} array of node elements.
 */
qtre.ColladaLoader.getChildNodesByNodeName = function (searchNode, nodeName)
{
	var children = [];
	var foundOne = false;

	if (searchNode.childNodes.length > 0)
	{
		for (var i = 0, len = searchNode.childNodes.length; i < len; i++)
		{
			if (searchNode.childNodes[i].nodeName == nodeName)
			{
				children.push(searchNode.childNodes[i]);
				foundOne = true;
			}
		}
	}

	// somehow need to specify if non were found, so
	// send back a null if that's the case.
	if (foundOne == false)
	{
		children = null;
	}

	return children;
}



/**
 @private
 static method of c3dl.ColladaLoader
 
 Turn a series of strings seperated by 'delimiter' into float values.
 used when we need to convert <translate>0.0 0.0 0.0</translate> into float
 values.
 
 @param {String} numbers A string which contains numbers such as "1.0 0.0 1.0 0.0       0.0 1.0 ..." note the spaces.
 @param {String} delimeter A string which is being used to separate the numbers.
 */
qtre.ColladaLoader.stringsToFloats = function (numbers, delimeter)
{
	var floatValues = [];

	// Get rid of leading and trailing spaces so they don't interfere with out split().
	// remove leading whitespace
	var trimmedNumbers = numbers.replace(/^\s+/, '');

	// remove trailing whitespace
	trimmedNumbers = trimmedNumbers.replace(/\s+$/, '');

	// remove superfluous whitespace between numbers
	// find one or more instances of whitespace and replace with a single space.
	trimmedNumbers = trimmedNumbers.replace(/\s+/g, ' ');

	// split each number into an element of an array, but they are still
	// strings, so convert them to float.
	var strValues = trimmedNumbers.split(delimeter);

	for (var i = 0, len = strValues.length; i < len; i++)
	{
		floatValues.push(parseFloat(strValues[i]));
	}

	return floatValues;
}